package com.ddfeiyu.aspect;


import com.alibaba.fastjson.JSON;
import com.ddfeiyu.agreement.ReconnectRequest;
import com.ddfeiyu.agreement.log.LogRequest;
import com.ddfeiyu.annotation.TraceLog;
import com.ddfeiyu.client.NettyClient;
import com.ddfeiyu.dto.Log;
import com.ddfeiyu.redis.util.RedisQueueUtil;
import com.ddfeiyu.util.NetUtils;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.*;

@Aspect
@Slf4j
public class TraceLogAspect {

    private static final String LONG_DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS";

    //默认线程池
    private static ExecutorService executorService = Executors.newFixedThreadPool(2);

    private static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

    private final Map<String,Channel> channelMap  = new ConcurrentHashMap<>();

    @Value("${spring.application.name}")
    private String app;
    @Autowired
    private RedisQueueUtil redisQueueUtil;


    // 严格模式  within(com..*) && @target(com.ddfeiyu.annotation.EnableTraceLog) && @annotation(com.ddfeiyu.annotation.TraceLog)
    // 中等模式  within(com..*) && @target(com.ddfeiyu.annotation.EnableTraceLog)
    // 最低模式  within(com..*)

    @Pointcut("within(com..*) && @target(com.ddfeiyu.annotation.EnableTraceLog) && @annotation(com.ddfeiyu.annotation.TraceLog)")
    public void pointcut() {
        log.info("------TraceLogAspect.pointcut");
    }


    @Around(value = "pointcut()")
    public Object doAround(ProceedingJoinPoint pjp) throws  Throwable{
        log.info("------TraceLogAspect.doAround--start，traceId:[ {} ]",TraceContext.traceId());
        long st = System.currentTimeMillis();
        try {
            Object result = pjp.proceed();
            return result;
        }catch (Exception e){
            log.error("------TraceLogAspect.doAround--exception，traceId:[ {} ]", TraceContext.traceId());
            handleLog(pjp,e, Log.STATUS_FAIL ,null, System.currentTimeMillis() - st);
            throw e;
        }finally {
            handleLog(pjp,null, Log.STATUS_SUCCESS ,null, System.currentTimeMillis() - st);
            log.info("------TraceLogAspect.doAround--end，traceId:[ {} ]", TraceContext.traceId());
        }
    }

    private void handleLog(final JoinPoint joinPoint, final Exception e,String status,  final Object result, long elapsedTime){
        String className = "" , methodName = "" ;
        String description = "" ;
        String traceId = "", busId = "";
        try {
            Class target = joinPoint.getTarget().getClass();
            /*EnableTraceLog logEnable = (EnableTraceLog) target.getAnnotation(EnableTraceLog.class);
            if(logEnable == null || !logEnable.enable()){
                return;
            }*/
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            TraceLog traceLog =  method.getAnnotation(TraceLog.class);
            if (traceLog == null) {
                description = "";
            }
            description = traceLog.description();
            // 设置方法名称
            className = target.getName();
            methodName = method.getName();
            String parameterTypes = Arrays.toString(method.getParameterTypes());

            Log fullTraceLog = new Log();
            fullTraceLog.setClazzName(className);
            fullTraceLog.setMethodName(methodName);
            fullTraceLog.setParameterTypes(parameterTypes);
            traceId = TraceContext.traceId();
            fullTraceLog.setTraceId(traceId);
            fullTraceLog.setLogTime(new SimpleDateFormat(LONG_DATE_TIME_PATTERN).format(new Date()));
            fullTraceLog.setApp(app);
            fullTraceLog.setHost(NetUtils.getLocalHost());
            fullTraceLog.setDescription(description);
            fullTraceLog.setStatus(status);
            fullTraceLog.setErrorMsg(Objects.nonNull(e) ? e.getMessage() : "");
            fullTraceLog.setArguments(handle( joinPoint.getArgs() ));
            fullTraceLog.setElapsedTime(elapsedTime);
            String logData = JSON.toJSONString(fullTraceLog);

            log.info("------TraceLogAspect.handleLog， app:[ {} ]， host:[ {} ],class:[ {} ]， method:[ {} ] ， traceId:[ {} ]， busId:[ {} ] ", fullTraceLog.getApp() ,fullTraceLog.getHost(),className  ,methodName, traceId ,busId );

            // TODO netty 连接 core
            connectServer(traceId, logData);
        }catch (Exception ex){
            log.error("------TraceLogAspect.handleLog,class:[ {} ]， method:[ {} ]， traceId:[ {} ]， busId:[ {} ] ",className  ,methodName, traceId ,busId , ex);
        }
    }

    private String handle(Object[] args){
        if (Objects.isNull(args) || args.length == 0){
            return null;
        }
        Object[] res = new Object[args.length];
        for (int i = 0; i < args.length ; i++) {
            if (!(args[i]  instanceof HttpServletRequest)){
                res[i] = args[i];
            }else{
                res[i] = null;
            }
        }
        return Arrays.toString(res);
    }

    private void connectServer(String traceId, String logData) throws Exception{
        // 1. 启动socket连接
        log.info("NettyClient连接服务开始CommandLineRunner  " );
        Channel channel = null;
        if (!channelMap.containsKey(traceId)){
            channel = getChannel(traceId);
            channelMap.put(traceId, channel);
        }else {
            channel = channelMap.get(traceId);
            if (!channel.isActive()){
                channelMap.remove(traceId);
                channel = getChannel(traceId);
                channelMap.put(traceId, channel);
            }
        }
        channel.writeAndFlush(new LogRequest(traceId,logData));
    }


    private Channel getChannel(String traceId) throws Exception{
        NettyClient nettyClient = new NettyClient(redisQueueUtil);
        Future<Channel> future = executorService.submit(nettyClient);
        Channel channel = future.get();
        if (null == channel) {
            throw new RuntimeException("NettyClient连接服务开始CommandLineRunner start error channel is null");
        }
        while (!nettyClient.isActive()){
            log.info("NettyClient启动服务 ...");
            Thread.sleep(500);
        }
        log.info("NettyClient连接服务开始CommandLineRunner完成 {}", channel.localAddress());

        // Channel状态定时巡检；3秒后每5秒执行一次
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            while (!nettyClient.isActive()) {
                log.info("NettyClient通信管道巡检：通信管道状态 " + nettyClient.isActive());
                try {
                    log.info("NettyClient通信管道巡检：断线重连[Begin]");
                    Channel freshChannel = executorService.submit(nettyClient).get();
                    if (null == traceId) {
                        continue;
                    }
                    freshChannel.writeAndFlush(new ReconnectRequest(traceId));
                } catch (InterruptedException | ExecutionException e) {
                    log.error("NettyClient通信管道巡检：断线重连[Error]");
                }
            }
        }, 3, 5, TimeUnit.SECONDS);
        return channel;
    }

}